/*
 * This source file is part of libRocket, the HTML/CSS Interface Middleware
 *
 * For the latest information, see http://www.librocket.com
 *
 * Copyright (c) 2008-2010 Nuno Silva
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 *
 */
#include <Rocket/Core/Core.h>
#include "Application/RocketRenderInterface.hpp"


#include <gl/glew.h>

#ifndef GL_CLAMP_TO_EDGE
#define GL_CLAMP_TO_EDGE 0x812F
#endif
// If built with the GL Easy Extension library we can compile geometry to VBO's
// http://www.opengl.org/sdk/libs/GLee/

#include <Core/Core.hpp>


////////////////////////////////////////////////////////////
// Snip/steal from SFML for debugging.
void glCheckError(const char* file, unsigned int line)
{
    // Get the last error
    GLenum errorCode = glGetError();
	
    if (errorCode != GL_NO_ERROR)
    {
        std::string fileString(file);
        std::string error = "unknown error";
        std::string description  = "no description";
		
        // Decode the error code
        switch (errorCode)
        {
            case GL_INVALID_ENUM :
            {
                error = "GL_INVALID_ENUM";
                description = "an unacceptable value has been specified for an enumerated argument";
                break;
            }
				
            case GL_INVALID_VALUE :
            {
                error = "GL_INVALID_VALUE";
                description = "a numeric argument is out of range";
                break;
            }
				
            case GL_INVALID_OPERATION :
            {
                error = "GL_INVALID_OPERATION";
                description = "the specified operation is not allowed in the current state";
                break;
            }
				
            case GL_STACK_OVERFLOW :
            {
                error = "GL_STACK_OVERFLOW";
                description = "this command would cause a stack overflow";
                break;
            }
				
            case GL_STACK_UNDERFLOW :
            {
                error = "GL_STACK_UNDERFLOW";
                description = "this command would cause a stack underflow";
                break;
            }
				
            case GL_OUT_OF_MEMORY :
            {
                error = "GL_OUT_OF_MEMORY";
                description = "there is not enough memory left to execute the command";
                break;
            }
				
            case GL_INVALID_FRAMEBUFFER_OPERATION_EXT :
            {
                error = "GL_INVALID_FRAMEBUFFER_OPERATION_EXT";
                description = "the object bound to FRAMEBUFFER_BINDING_EXT is not \"framebuffer complete\"";
                break;
            }
        }
		
        // Log the error
        LOG( ERROR ) << "An internal OpenGL call failed in "
		<< fileString.substr(fileString.find_last_of("\\/") + 1) << " (" << line << ") : "
		<< error << ", " << description
		<< std::endl;
    }
}

#ifdef SFML_DEBUG

// In debug mode, perform a test on every OpenGL call
#define glCheck(call) ((call), glCheckError(__FILE__, __LINE__))

#else

// Else, we don't add any overhead
#define glCheck(call) (call)

#endif




class RocketSFMLRendererGeometryHandler
{
public:
	GLuint VertexID, IndexID;
	int NumVertices;
	Rocket::Core::TextureHandle Texture;

	RocketSFMLRendererGeometryHandler() : VertexID(0), IndexID(0), NumVertices(0), Texture(0)
	{
	};

	~RocketSFMLRendererGeometryHandler()
	{
		if(VertexID)
			glDeleteBuffers(1, &VertexID);

		if(IndexID)
			glDeleteBuffers(1, &IndexID);

		VertexID = IndexID = 0;
	};
};


struct RocketSFMLRendererVertex
{
	sf::Vector2f Position, TexCoord;
	sf::Color Color;
};

Application::RocketRenderInterface::RocketRenderInterface()
{
	// Both make sure shaders are available and also make sure
	// GLEW is initialized.
	if( sf::Shader::isAvailable() )
	{
		LOG( INFO ) << "Shaders are available.";
	}
}

void Application::RocketRenderInterface::SetWindow(sf::RenderWindow *Window)
{
	MyWindow = Window;

	Resize();
};

sf::RenderWindow *Application::RocketRenderInterface::GetWindow()
{
	return MyWindow;
};

void Application::RocketRenderInterface::Resize()
{
	MyWindow->setActive(true);
	//MyWindow->SaveGLStates();

	static sf::View View;
	sf::Vector2u	size	=	MyWindow->getSize();

	View.setViewport(sf::FloatRect(0, (float)size.x, (float)size.y, 0));
	MyWindow->setView(View);

	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(0, size.x, size.y, 0, -1, 1);
	glMatrixMode(GL_MODELVIEW);

	glViewport(0, 0, size.x, size.y );

	//MyWindow->RestoreGLStates();
};

// Called by Rocket when it wants to render geometry that it does not wish to optimise.
void Application::RocketRenderInterface::RenderGeometry(Rocket::Core::Vertex* vertices, int num_vertices, int* indices, int num_indices, const Rocket::Core::TextureHandle texture, const Rocket::Core::Vector2f& translation)
{
	MyWindow->setActive();

	glPushMatrix();
	glTranslatef(translation.x, translation.y, 0);

	std::vector<Rocket::Core::Vector2f> Positions(num_vertices);
	std::vector<Rocket::Core::Colourb> Colors(num_vertices);
	std::vector<Rocket::Core::Vector2f> TexCoords(num_vertices);

	for(int  i = 0; i < num_vertices; i++)
	{
		Positions[i] = vertices[i].position;
		Colors[i] = vertices[i].colour;
		TexCoords[i] = vertices[i].tex_coord;
	};

	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_COLOR_ARRAY);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);

	glVertexPointer(2, GL_FLOAT, 0, &Positions[0]);
	glColorPointer(4, GL_UNSIGNED_BYTE, 0, &Colors[0]);
	glTexCoordPointer(2, GL_FLOAT, 0, &TexCoords[0]);

	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	sf::Texture *image = (sf::Texture *)texture;

	if(image)
	{
		sf::Texture::bind( image );
	}
	else
	{
		glDisableClientState(GL_TEXTURE_COORD_ARRAY);
		glBindTexture(GL_TEXTURE_2D, 0);
	};

	glEnable( GL_TEXTURE_2D );
	glDrawElements(GL_TRIANGLES, num_indices, GL_UNSIGNED_INT, indices);

	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_COLOR_ARRAY);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);

	glColor4f(1, 1, 1, 1);

	glPopMatrix();
	glDisable( GL_TEXTURE_2D );
}

// Called by Rocket when it wants to compile geometry it believes will be static for the forseeable future.		
Rocket::Core::CompiledGeometryHandle Application::RocketRenderInterface::CompileGeometry(Rocket::Core::Vertex* vertices,
																		   int num_vertices, int* indices,
																		   int num_indices,
																		   const Rocket::Core::TextureHandle texture)
{
	MyWindow->setActive();

	std::vector<RocketSFMLRendererVertex> Data(num_vertices);

	for(unsigned long i = 0; i < Data.size(); i++)
	{
		Data[i].Position = *(sf::Vector2f*)&vertices[i].position;
		Data[i].TexCoord = *(sf::Vector2f*)&vertices[i].tex_coord;
		Data[i].Color = sf::Color(vertices[i].colour.red, vertices[i].colour.green,
			vertices[i].colour.blue, vertices[i].colour.alpha);
	};

	RocketSFMLRendererGeometryHandler *Geometry = new RocketSFMLRendererGeometryHandler();
	Geometry->NumVertices = num_indices;

	glCheck( glGenBuffers( 1, &Geometry->VertexID ) );
	glCheck( glBindBuffer(GL_ARRAY_BUFFER, Geometry->VertexID) );
	glCheck( glBufferData(GL_ARRAY_BUFFER, sizeof(RocketSFMLRendererVertex) * num_vertices, &Data[0], GL_STATIC_DRAW) );

	glCheck( glGenBuffers(1, &Geometry->IndexID) );
	glCheck( glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, Geometry->IndexID) );
	glCheck( glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(int) * num_indices, indices, GL_STATIC_DRAW) );

	glCheck( glBindBuffer( GL_ARRAY_BUFFER, 0 ) );
	glCheck( glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 ) );

	Geometry->Texture = texture;

	return (Rocket::Core::CompiledGeometryHandle)Geometry;
}

// Called by Rocket when it wants to render application-compiled geometry.		
void Application::RocketRenderInterface::RenderCompiledGeometry(Rocket::Core::CompiledGeometryHandle geometry, const Rocket::Core::Vector2f& translation)
{
	MyWindow->setActive();

	RocketSFMLRendererGeometryHandler *RealGeometry = (RocketSFMLRendererGeometryHandler *)geometry;

	glCheck( glPushMatrix() );
	glCheck( glTranslatef(translation.x, translation.y, 0) );
	glCheck( glEnable(GL_BLEND) );
	glCheck( glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) );

	sf::Texture *image = (sf::Texture *)RealGeometry->Texture;

	if( image )
	{
		sf::Texture::bind( image );
	}
	else
	{
		glCheck( glBindTexture( GL_TEXTURE_2D, 0 ) );
	};

	glCheck( glEnable( GL_TEXTURE_2D ) );
	glCheck( glEnableClientState( GL_VERTEX_ARRAY ) );
	glCheck( glEnableClientState( GL_TEXTURE_COORD_ARRAY ) );
	glCheck( glEnableClientState( GL_COLOR_ARRAY ) );

	#define BUFFER_OFFSET(x) ((char*)0 + x)

	glCheck( glBindBuffer(GL_ARRAY_BUFFER, RealGeometry->VertexID) );
	glCheck( glVertexPointer(2, GL_FLOAT, sizeof(RocketSFMLRendererVertex), BUFFER_OFFSET(0)) );
	glCheck( glTexCoordPointer(2, GL_FLOAT, sizeof(RocketSFMLRendererVertex), BUFFER_OFFSET(sizeof(sf::Vector2f))) );
	glCheck( glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(RocketSFMLRendererVertex), BUFFER_OFFSET(sizeof(sf::Vector2f[2]))) );

	glCheck( glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, RealGeometry->IndexID) );
	glCheck( glDrawElements(GL_TRIANGLES, RealGeometry->NumVertices, GL_UNSIGNED_INT, BUFFER_OFFSET(0)) );

	glCheck( glBindBuffer( GL_ELEMENT_ARRAY_BUFFER, 0 ) );
	glCheck( glBindBuffer(GL_ARRAY_BUFFER, 0) );

	glCheck( glDisableClientState(GL_COLOR_ARRAY) );
	glCheck( glDisableClientState(GL_TEXTURE_COORD_ARRAY) );
	glCheck( glDisableClientState(GL_VERTEX_ARRAY) );

	glCheck( glColor4f(1, 1, 1, 1) );

	glCheck( glPopMatrix() );
	glCheck( glDisable( GL_TEXTURE_2D ) );
}

// Called by Rocket when it wants to release application-compiled geometry.		
void Application::RocketRenderInterface::ReleaseCompiledGeometry(Rocket::Core::CompiledGeometryHandle geometry)
{
	MyWindow->setActive();

	delete (RocketSFMLRendererGeometryHandler *)geometry;
}

// Called by Rocket when it wants to enable or disable scissoring to clip content.		
void Application::RocketRenderInterface::EnableScissorRegion(bool enable)
{
	MyWindow->setActive();

	if (enable)
		glEnable(GL_SCISSOR_TEST);
	else
		glDisable(GL_SCISSOR_TEST);
}

// Called by Rocket when it wants to change the scissor region.		
void Application::RocketRenderInterface::SetScissorRegion(int x, int y, int width, int height)
{
	MyWindow->setActive();

	sf::Vector2u	size	=	MyWindow->getSize();
	glScissor(x, size.y - (y + height), width, height);
}

// Called by Rocket when a texture is required by the library.		
bool Application::RocketRenderInterface::LoadTexture(Rocket::Core::TextureHandle& texture_handle, Rocket::Core::Vector2i& texture_dimensions, const Rocket::Core::String& source)
{
	MyWindow->setActive();
	
	Rocket::Core::FileInterface* file_interface = Rocket::Core::GetFileInterface();
	Rocket::Core::FileHandle file_handle = file_interface->Open(source);
	if (file_handle == 0)
		return false;

	file_interface->Seek(file_handle, 0, SEEK_END);
	size_t buffer_size = file_interface->Tell(file_handle);
	file_interface->Seek(file_handle, 0, SEEK_SET);
	
	char* buffer = new char[buffer_size];
	file_interface->Read(buffer, buffer_size, file_handle);
	file_interface->Close(file_handle);

	sf::Texture *image = new sf::Texture();

	if( !image->loadFromMemory(buffer, buffer_size))
	{
		delete buffer;
		delete image;

		LOG( INFO ) << "Failed to load: " << source.CString();
		return false;
	};
	delete buffer;

	texture_handle = (Rocket::Core::TextureHandle) image;

	sf::Vector2u	size	=	image->getSize();
	texture_dimensions = Rocket::Core::Vector2i( size.x, size.y );

	LOG( INFO ) << "Loaded: " << source.CString();
	return true;
}

// Called by Rocket when a texture is required to be built from an internally-generated sequence of pixels.
bool Application::RocketRenderInterface::GenerateTexture(Rocket::Core::TextureHandle& texture_handle, const Rocket::Core::byte* source, const Rocket::Core::Vector2i& source_dimensions)
{
	MyWindow->setActive();

	sf::Texture *image = new sf::Texture();

	if( !image->create( source_dimensions.x, source_dimensions.y ) )
	{
		delete image;
		return false;
	}
	image->update( source, source_dimensions.x, source_dimensions.y, 0, 0 );

	texture_handle = (Rocket::Core::TextureHandle)image;

	return true;
}

// Called by Rocket when a loaded texture is no longer required.		
void Application::RocketRenderInterface::ReleaseTexture(Rocket::Core::TextureHandle texture_handle)
{
	delete (sf::Texture *)texture_handle;
}
